<!DOCTYPE html>
<html lang="zh">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="apple-touch-icon" href="apple-touch-icon.png">
    <link rel="shortcut icon" href="favicon.ico" />
    <link rel="manifest" href="a3.webmanifest">
    <title>A3 Slide</title>
  </head>
  <body>
    <link rel="stylesheet" href="css/main.css"/>
    <script src="js/Theme.js"></script>
    <script src="js/ammo.js/build/ammo.wasm.js"></script>
    <div class="div-fullscreen"><div id="container"></div></div>
    <script type="module">
      import * as THREE from "./js/three.js/build/three.module.js";
      import Stats from './jsm/libs/stats.module.js';
		  import { OrbitControls } from './jsm/controls/OrbitControls.js';
      import { GUI } from './jsm/libs/dat.gui.module.js';
      // import { ConvexGeometry } from './jsm/geometries/ConvexGeometry.js';
      
      // Graphics variables
      let container, stats;
      let camera, controls, scene, renderer;
      let ambientLight;

      let textureLoader;
      const clock = new THREE.Clock();

      // Physics variables
      const gravityConstant = - 9.8;
      let collisionConfiguration;
      let dispatcher;
      let broadphase;
      let solver;
      let softBodySolver;
      let physicsWorld;
      const rigidBodies = [];
      const margin = 0.05;
      let transformAux1;

      // gui
      let gui;

      // objects
      // table
      let table;
      const tableHeight = 0.5;
      const tableWidth = 20;
      const tableLength = 20;
      const tableMass = 1;

      // gyto
      let gyto, mainBody;

      // 地形
      // Heightfield parameters
			const terrainWidthExtents = 100;
			const terrainDepthExtents = 100;
			const terrainWidth = 128;
			const terrainDepth = 128;
			const terrainHalfWidth = terrainWidth / 2;
			const terrainHalfDepth = terrainDepth / 2;
			const terrainMaxHeight = 8;
			const terrainMinHeight = - 2;

      let heightData;
      let ammoHeightData;
      let terrainMesh;

      let testMesh ;

      heightData = generateHeight2( terrainWidth, terrainDepth, terrainMinHeight, terrainMaxHeight );
      
      let params = {
        // 背景
        sceneBackGroundColor: 0xbfd1e5,
        sceneLight:0x404040,
        cameraFov:60,
        cameraFar:2000,
        cameraNear:0.2,
        wireframe:false,
        // 仿真
        simSpeed:1,
        simSpeeds:[0.01, 0.1, 0.2, 0.5, 1, 2, 5, 10],
        simStart:true,
        simPause:false,
        simReset:false,

        // 陀螺
        coneHeight:0.1, gytoRadius: 1, gytoHeight:1,
        bevelSegments: 1, steps: 1, bevelSize: 0.1, bevelThickness: 0.2,
      }


      Ammo().then( function ( AmmoLib ) {

        Ammo = AmmoLib;

        init();
        animate();

      } );


      function init() {

        initGraphics();

        initPhysics();

        createObjects();

        initInput();

        setupGUI();

      }

      function reset(){

      }

      
      function initGraphics() {

        container = document.getElementById( 'container' );
        
        camera = new THREE.PerspectiveCamera(
          params.cameraFov, window.innerWidth / window.innerHeight, 
          params.cameraNear, params.cameraFar
        );

        scene = new THREE.Scene();
        scene.background = new THREE.Color( params.sceneBackGroundColor );

        camera.position.set( - 10, 20, 10 );

        renderer = new THREE.WebGLRenderer({ alpha: true });
        renderer.setClearColor(0XFFFFFF, 0);
        renderer.setPixelRatio( window.devicePixelRatio );
        renderer.setSize( window.innerWidth, window.innerHeight );
        renderer.shadowMap.enabled = true;
        container.appendChild( renderer.domElement );

        controls = new OrbitControls( camera, renderer.domElement );
        controls.target.set( 0, 2, 0 );
        controls.update();

        textureLoader = new THREE.TextureLoader();

        ambientLight = new THREE.AmbientLight( params.sceneLight );
        scene.add( ambientLight );

        const light = new THREE.DirectionalLight( 0xffffff, 1 );
        light.position.set( 10, 10, 10 );
        light.castShadow = true;
        const d = 10;
        light.shadow.camera.left = - d;
        light.shadow.camera.right = d;
        light.shadow.camera.top = d;
        light.shadow.camera.bottom = - d;

        light.shadow.camera.near = 2;
        light.shadow.camera.far = 50;

        light.shadow.mapSize.x = 1024;
        light.shadow.mapSize.y = 1024;

        scene.add( light );

        stats = new Stats();
        stats.domElement.style.position = 'absolute';
        stats.domElement.style.top = '0px';
        container.appendChild( stats.domElement );

        //

        window.addEventListener( 'resize', onWindowResize );

      }

      function initPhysics() {

        // Physics configuration

				collisionConfiguration = new Ammo.btSoftBodyRigidBodyCollisionConfiguration();
        dispatcher = new Ammo.btCollisionDispatcher( collisionConfiguration );
        broadphase = new Ammo.btDbvtBroadphase();
        solver = new Ammo.btSequentialImpulseConstraintSolver();
        softBodySolver = new Ammo.btDefaultSoftBodySolver();
        physicsWorld = new Ammo.btSoftRigidDynamicsWorld( dispatcher, broadphase, solver, collisionConfiguration, softBodySolver );
        physicsWorld.setGravity( new Ammo.btVector3( 0, gravityConstant, 0 ) );
        physicsWorld.getWorldInfo().set_m_gravity( new Ammo.btVector3( 0, gravityConstant, 0 ) );

				transformAux1 = new Ammo.btTransform();

      }

      
      function createObjects() {
        const pos = new THREE.Vector3();
        const quat = new THREE.Quaternion();

        // Ground
        pos.set( 0, - 0.5, 0 );
        quat.set( 0, 0, 0, 1 );
        const ground = createParalellepiped( 40, 1, 40, 0, pos, quat, new THREE.MeshPhongMaterial( { color: 0xFFFFFF } ) );
        ground.castShadow = true;
        ground.receiveShadow = true;
        textureLoader.load( "textures/grid.png", function ( texture ) {

          texture.wrapS = THREE.RepeatWrapping;
          texture.wrapT = THREE.RepeatWrapping;
          texture.repeat.set( 40, 40 );
          ground.material.map = texture;
          ground.material.needsUpdate = true;

        } );

        // table
        pos.set(0, tableHeight / 2, 0);
        quat.set(0, 0, 0, 1);
        table = createParalellepiped( tableWidth, tableHeight, tableLength, tableMass, 
          pos, quat, new THREE.MeshPhongMaterial( { color: 0x1982cc } ) 
        );
        table.castShadow = true;
        table.receiveShadow = true;
        table.userData.physicsBody.setFriction( 0.5 );
        table.userData.physicsBody.setRestitution( 1 );

        // 柱子
        const poleHeight = 10;
        pos.set(0, tableHeight + poleHeight / 2, 0);
        quat.set(0, 0, 0, 1);
        const pole = createCylindered(1, poleHeight, 1, pos, quat, createMaterial());
        // 空心圆环

        pos.set(0, tableHeight + 1, 0);
        quat.set(0, 0, 0, 1);
        var torusRadius = 1.8;
        var torusTube = 0.2;
        
        var i = 0;
        while(i < 2 && torusRadius > 1){
          pos.set(0, tableHeight + 1 + torusTube*i*2, 0);
          quat.set(0, 0, 0, 1);
          const torus = createTorus(torusRadius , torusTube, 0.1, pos, quat);
          // torusRadius /= 2;
          i++;
          // torus.userData.physicsBody.setAngularVelocity(new Ammo.btVector3(0, Math.PI * 2, 0));
        }

        // 滑梯
        pos.set(5, tableHeight + 1, 0.5);
        quat.set(0, 0, 0, 1);
        // quat.setFromAxisAngle(new THREE.Vector3(1, 0, 0), Math.PI);
        const slider = createSlider(2, 10, 1.8, 0, pos, quat);
        slider.castShadow = true;
        slider.receiveShadow = true;
        slider.userData.physicsBody.setFriction( 0.1 );
        // slider.userData.physicsBody.setAngularVelocity(new Ammo.btVector3(0, Math.PI/10, 0));
        // 球
        pos.set(6, tableHeight + poleHeight + 2, 0);
        const ball = createSphered(0.3, 1, pos, quat, createMaterial());
        ball.castShadow = true;
        ball.receiveShadow = true;
        ball.userData.physicsBody.setFriction( 0.5 );
        // ball.userData.physicsBody.setLinearVelocity(new Ammo.btVector3(1, 1, 1));
      }

      function createMaterial() {

        return new THREE.MeshPhongMaterial( { color: createRandomColor(), transparent: true, opacity: 0.5} );

      }

      function createRandomColor() {

        return Math.floor( Math.random() * ( 1 << 24 ) );

      }

      // 滑梯
      function createSlider(width, length, depth, mass, pos, quat){
        const slider = new THREE.Group();

        var compound = new Ammo.btCompoundShape(true);
        var transform  = new Ammo.btTransform();
        transform.setIdentity();

        // 楼梯形状
        const extrudePoints = [];
        const slideLength = length * 2;
        const positions = [];
				for ( let i = 0; i < slideLength; i ++ ) {
          const x = i - length;
          const y = x*x / 16;
					extrudePoints.push( new THREE.Vector3( x, y, 0) );
          positions.push(x, y, 0);
				}

        // 拉伸线
        const lineGeometry = new THREE.BufferGeometry();
        lineGeometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
        lineGeometry.rotateY(-Math.PI/2);
        const lineMesh = new THREE.Line(lineGeometry, new THREE.LineDashedMaterial( { color: 0xff0000, dashSize: 1, gapSize: 0.5 } ) );
        lineMesh.computeLineDistances();
        slider.add(lineMesh);
        testMesh = lineMesh;

				const extrudeLine = new THREE.CatmullRomCurve3( extrudePoints );

        const extrudeSettings = { 
          steps: 200,
          bevelEnabled: false, 
          extrudePath: extrudeLine};

        // 滑梯底部
        const slideBottom = new THREE.Shape();
        ( function roundedRect( ctx, x, y, width, height, depth ) {

          ctx.moveTo( x, y );
          ctx.lineTo( x + width, y );
          ctx.lineTo( x + width, y + height  - depth );
          ctx.lineTo( x, y + height  - depth );
          ctx.lineTo( x , y );

        } )( slideBottom, 0, 0, width, width, depth );

        const slideLeft = new THREE.Shape();
        ( function roundedRect( ctx, x, y, width, height, depth ) {

          ctx.moveTo( x, y );
          ctx.lineTo( x + width - depth, y );
          ctx.lineTo( x + width - depth, y + height );
          ctx.lineTo( x, y + height);
          ctx.lineTo( x , y );

        } )( slideLeft, 0, 0, width, width, depth );
        
        const bottomGeometry = new THREE.ExtrudeGeometry( slideBottom, extrudeSettings );
        bottomGeometry.rotateY(-Math.PI/2);
        
				const slideBottomMesh = new THREE.Mesh( bottomGeometry, createMaterial() );

        slider.add(slideBottomMesh);
        const slideBottomShape = createConvexHullPhysicsShape(bottomGeometry.attributes.position.array);
        compound.addChildShape(transform, slideBottomShape);

        const leftGeometry = new THREE.ExtrudeGeometry( slideLeft, extrudeSettings );
        leftGeometry.rotateY(-Math.PI/2);
				const leftMesh = new THREE.Mesh( leftGeometry, createMaterial() );

        slider.add(leftMesh);
        // const slideLeftShape = createConvexHullPhysicsShape(leftGeometry.attributes.position.array);
        // compound.addChildShape(transform, slideLeftShape);

        const rightGeometry = new THREE.ExtrudeGeometry( slideLeft, extrudeSettings );
        rightGeometry.translate(0, 0, -width);
        rightGeometry.rotateY(-Math.PI/2);
				const rigthMesh = new THREE.Mesh( rightGeometry, createMaterial() );

        slider.add(rigthMesh);
        // const slideRightShape = createConvexHullPhysicsShape(rightGeometry.attributes.position.array);
        // compound.addChildShape(transform, slideRightShape);

        // slider.position.copy(pos);
        // slider.quaternion.copy(quat);
        // scene.add(slider);
        const sliderBody = createRigidBody(slider, compound, mass, pos, quat);
        sliderBody.setFriction(0.5);
        // torusBody.setAngularVelocity(new Ammo.btVector3(0, -Math.PI, 0));
        return slider;
      }

      // 空心圆环
      function createTorus(radius, tube, mass, pos, quat ){
        const tPos = pos.clone();
        const tQuat = quat.clone();
        const torusPart = 8;
        // 圆环
        const torusGroup = new THREE.Group();
        const torusRadius = radius;
        const torusTube = tube;
        var compound = new Ammo.btCompoundShape(true);
        var transform  = new Ammo.btTransform();
        transform.setIdentity();

        for(var i = 0; i < torusPart; i ++){
          var torusGeometry = new THREE.TorusGeometry( torusRadius, torusTube, 48, 48, Math.PI * 2 / torusPart);
          torusGeometry.rotateZ(i * ((Math.PI * 2) / torusPart));
          const torus = new THREE.Mesh(torusGeometry, createMaterial());
          pos.set(0,0,0);
          quat.setFromAxisAngle(new THREE.Vector3(1, 0, 0), Math.PI / 2);
          torus.position.copy(pos);
          torus.quaternion.copy(quat);
          torusGroup.add(torus);

          const torusShape = createConvexHullPhysicsShape(torusGeometry.attributes.position.array);
          transform.setOrigin(new Ammo.btVector3(pos.x, pos.y, pos.z));
          transform.setRotation(new Ammo.btQuaternion( quat.x, quat.y, quat.z, quat.w ));
          compound.addChildShape(transform, torusShape);
        }
        
        const torusBody = createRigidBody(torusGroup, compound, mass, tPos, tQuat);
        torusBody.setFriction(0.5);
        // torusBody.setAngularVelocity(new Ammo.btVector3(0, -Math.PI, 0));
        return torusGroup;
      }

      function updateGyto(){

      }

      function setWireframe(node, wireframe){
        if(node.type === 'Mesh'){
          node.material.wireframe = wireframe;
        }
        else if(node.children.length > 0){
          for(var i = 0, il = node.children.length; i < il; i++){
            setWireframe(node.children[i], wireframe);
          }
        }
      }

      function generateHeight2( width, depth, minHeight, maxHeight ) {

        const size = width * depth;
        const data = new Float32Array( size );

        const hRange = maxHeight - minHeight;
        const scale = hRange / Math.max(width, depth);
        const radius = Math.min(width, depth) / 2.828;

        let p = 0;

        for ( let j = 0; j < depth; j ++ ) {

          for ( let i = 0; i < width; i ++ ) {
            const w = i - width / 2;
            const h = j - depth / 2;
            if( w*w + h*h >= radius * radius){
              data[p] = 0;
            }
            else{
              const height = (Math.sqrt(radius * radius - (w * w + h * h)) / 10);

              data[ p ] = height;
            }
            

            p ++;

          }

        }

        return data;

        }

      function generateHeight( width, depth, minHeight, maxHeight ) {

        // Generates the height data (a sinus wave)

        const size = width * depth;
        const data = new Float32Array( size );

        const hRange = maxHeight - minHeight;
        const w2 = width / 2;
        const d2 = depth / 2;
        const phaseMult = 12;

        let p = 0;

        for ( let j = 0; j < depth; j ++ ) {

          for ( let i = 0; i < width; i ++ ) {

            const radius = Math.sqrt(
              Math.pow( ( i - w2 ) / w2, 2.0 ) +
                Math.pow( ( j - d2 ) / d2, 2.0 ) );

            const height = ( Math.sin( radius * phaseMult ) + 1 ) * 0.5 * hRange + minHeight;

            data[ p ] = height;

            p ++;

          }

        }

        return data;

      }

      function initInput() {

        window.addEventListener( 'keydown', function ( event ) {


        } );

        window.addEventListener( 'keyup', function () {

        } );

      }


      function setupGUI(){
        
        gui = new GUI();
        // 场景
        const guiScene = gui.addFolder('场景');
        guiScene.addColor(params, 'sceneBackGroundColor').name('背景色').onChange((val)=>{
          scene.background.set(val);
        });
        guiScene.addColor(params, 'sceneLight').name('环境光').onChange((val)=>{
          ambientLight.color.set(val);
        });
        guiScene.add(params, 'cameraFov', 1, 360, 1).name('相机视角').onChange((val)=>{
          camera.fov = val;
          camera.updateProjectionMatrix();
        });
        guiScene.add(params, 'cameraFar', 10, 9999, 1).name('相机远景').onChange((val)=>{
          camera.far = val;
          camera.updateProjectionMatrix();
        });
        guiScene.add(params, 'cameraNear', 0.01, 10, 0.01).name('相机近景').onChange((val)=>{
          camera.near = val;
          camera.updateProjectionMatrix();
        });
        guiScene.add(params, 'wireframe', 0.01, 10, 0.01).name('显示线框').onChange((val)=>{
          setWireframe(scene, val);
        });
        // 仿真
        const guiSimulation = gui.addFolder('仿真');
        const guiSimStart = guiSimulation.add(params, 'simStart').name('开始').onChange((val)=>{
          if(val){
            guiSimStart.name('开始');
            guiSimReset.setValue(false);
          }
          else{
            guiSimStart.name('停止');
          }
        });
        const guiSimPause = guiSimulation.add(params, 'simPause').name('暂停').onChange((val)=>{
          if(!val){
            guiSimPause.name('暂停');
          }
          else{
            guiSimPause.name('继续');
          }
        });
        const guiSimReset = guiSimulation.add(params, 'simReset').name('重置').onChange((val)=>{
          if(val){
            reset();
            guiSimStart.setValue(true);
            guiSimPause.setValue(false);
          }
        });
        guiSimulation.add(params, 'simSpeed').name('仿真速度').options(params.simSpeeds);
        // 陀螺

      }

      function onWindowResize() {

        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();

        renderer.setSize( window.innerWidth, window.innerHeight );

      }

      function createConvexHullPhysicsShape( coords ) {

        const shape = new Ammo.btConvexHullShape();
        var tempBtVec3_1 = new Ammo.btVector3( 0, 0, 0 );
        for ( let i = 0, il = coords.length; i < il; i += 3 ) {

          tempBtVec3_1.setValue( coords[ i ], coords[ i + 1 ], coords[ i + 2 ] );
          const lastOne = ( i >= ( il - 3 ) );
          shape.addPoint( tempBtVec3_1, lastOne );

        }

        shape.setMargin(margin);
        return shape;

      }

      function createTrianglePhysicsShape(coords){
        // console.log(coords)
        const triMesh = new Ammo.btTriangleMesh();
        const removeDuplicateVertices = true;
        for ( let i = 0, il = coords.length - 9; i < il; i += 9 ) {
          const a = new Ammo.btVector3(coords[i],   coords[i+1], coords[i+2]);
          const b = new Ammo.btVector3(coords[i+3], coords[i+4], coords[i+5]);
          const c = new Ammo.btVector3(coords[i+6], coords[i+7], coords[i+8]);

          triMesh.addTriangle(a, b, c, removeDuplicateVertices);
        }

        const calcAabb = true;
        const buildBvh = true;
        const shape = new Ammo.btConvexTriangleMeshShape(triMesh, calcAabb, buildBvh);
    
        return shape;
      }

      function createParalellepiped( sx, sy, sz, mass, pos, quat, material ) {

        const threeObject = new THREE.Mesh( new THREE.BoxGeometry( sx, sy, sz, 1, 1, 1 ), material );
        const shape = new Ammo.btBoxShape( new Ammo.btVector3( sx * 0.5, sy * 0.5, sz * 0.5 ) );
        shape.setMargin( margin );

        createRigidBody( threeObject, shape, mass, pos, quat );

        return threeObject;

      }

      function createSphered( r, mass, pos, quat, material ) {

        const threeObject = new THREE.Mesh( new THREE.SphereGeometry( r, 24, 24 ), material );
        const shape = new Ammo.btSphereShape( r );
        shape.setMargin( margin );

        createRigidBody( threeObject, shape, mass, pos, quat );

        return threeObject;

      }

      function createConed ( radius, height, mass, pos, quat, material ){
        var threeObject = new THREE.Mesh( new THREE.CylinderGeometry( 0 , radius, height, 24), material );
        var shape = new Ammo.btConeShape( radius, height );
        shape.setMargin( margin );

        createRigidBody( threeObject, shape, mass, pos, quat );

        return threeObject;
      }

      function createCylindered ( radius, height, mass, pos, quat, material ){
        var threeObject = new THREE.Mesh( new THREE.CylinderGeometry( radius , radius, height, 24), material );
        var shape = new Ammo.btConeShape( radius, height );
        shape.setMargin( margin );

        createRigidBody( threeObject, shape, mass, pos, quat );

        return threeObject;
      }

      function createRigidBody( threeObject, physicsShape, mass, pos, quat, inertia=undefined ) {

        threeObject.position.copy( pos );
        threeObject.quaternion.copy( quat );

        const transform = new Ammo.btTransform();
        transform.setIdentity();
        transform.setOrigin( new Ammo.btVector3( pos.x, pos.y, pos.z ) );
        transform.setRotation( new Ammo.btQuaternion( quat.x, quat.y, quat.z, quat.w ) );
        const motionState = new Ammo.btDefaultMotionState( transform );

        var localInertia = new Ammo.btVector3( 0, 0, 0 );
        if(inertia){
          localInertia = inertia;
        }

        physicsShape.calculateLocalInertia( mass, inertia )
        

        const rbInfo = new Ammo.btRigidBodyConstructionInfo( mass, motionState, physicsShape, localInertia );
        const body = new Ammo.btRigidBody( rbInfo );

        threeObject.userData.physicsBody = body;

        scene.add( threeObject );

        if ( mass > 0 ) {

          rigidBodies.push( threeObject );

          // Disable deactivation
          body.setActivationState( 4 );

        }

        physicsWorld.addRigidBody( body );

        return body;
      }

      function animate() {

        requestAnimationFrame( animate );

        render();
        stats.update();

      }

      function render() {

        const deltaTime = clock.getDelta() * parseFloat(params.simSpeed);
        updatePhysics( deltaTime );
        
        renderer.setViewport( 0, 0,  window.innerWidth, window.innerHeight );
        renderer.render( scene, camera ); 
      }

      function updatePhysics( deltaTime ) {
        // testMesh.rotateZ(Math.PI/180)
        // Step world
        physicsWorld.stepSimulation( deltaTime );

        // Update rigid bodies
        for ( let i = 0, il = rigidBodies.length; i < il; i ++ ) {

          const objThree = rigidBodies[ i ];
          const objPhys = objThree.userData.physicsBody;
          const ms = objPhys.getMotionState();
          if ( ms ) {

            ms.getWorldTransform( transformAux1 );
            const p = transformAux1.getOrigin();
            const q = transformAux1.getRotation();
            objThree.position.set( p.x(), p.y(), p.z() );
            objThree.quaternion.set( q.x(), q.y(), q.z(), q.w() );

          }
        }
        
      }
      
    </script>
  </body>
</html>